package tcln.gxtx.client.widget.treegrid;

import tcln.gxtx.client.widget.grid.editor.ExtendedColumnModel;

import com.extjs.gxt.ui.client.data.ModelData;
import com.extjs.gxt.ui.client.event.DomEvent;
import com.extjs.gxt.ui.client.event.EditorEvent;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.GridEvent;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.store.Record;
import com.extjs.gxt.ui.client.widget.grid.CellEditor;
import com.extjs.gxt.ui.client.widget.grid.EditorSupport;
import com.extjs.gxt.ui.client.widget.grid.Grid;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Event;

/**
 * A simple extension for an {@link EditorSupport} which customizes the lookup of cell editor.
 * 
 * @param <M>
 *            tree model
 * @author Truong Chau Lien Ngoc (lienngoc@gmail.com)
 */
public class ExtendedEditorSupport<M extends ModelData> extends EditorSupport<M> {

    /**
     * {@inheritDoc}
     */
    @Override
    // LNT [Jun 1, 2010]: This method is almost the same as the superclass's version except for the lookup for
    // correct CellEditor.
    protected void deferStartEditing(M m, String field, int row, int col) {

        editing = true;

        // lookup for cell editor
        CellEditor ed = null;
        if (cm instanceof ExtendedColumnModel) {
            ((ExtendedColumnModel) cm).getEditorByContext(m, field, row, col);
        } else {
            ed = cm.getEditor(col);
        }
        if (ed == null) {
            return; // don't allow to edit if editor is not found
        }

        // the following code is copied from the superclass
        // ---------------------------------------------------------------------------------------------------------

        ed.row = row;
        ed.col = col;

        if (!ed.isRendered()) {
            ed.render((com.google.gwt.user.client.Element) grid.getView().getEditorParent());
        }

        if (editorListener == null) {
            editorListener = new Listener<DomEvent>() {
                public void handleEvent(DomEvent e) {
                    if (e.getType() == Events.Complete) {
                        EditorEvent ee = (EditorEvent) e;
                        onEditComplete((CellEditor) ee.getEditor(), ee.getValue(), ee.getStartValue());
                    } else if (e.getType() == Events.SpecialKey) {
                        grid.getSelectionModel().onEditorKey(e);
                    } else if (e.getType() == Events.CancelEdit) {
                        EditorEvent ee = (EditorEvent) e;
                        onEditCancel((CellEditor) ee.getEditor(), ee.getValue(), ee.getStartValue());
                    }
                }
            };
        }

        ed.addListener(Events.Complete, editorListener);
        ed.addListener(Events.SpecialKey, editorListener);
        ed.addListener(Events.CancelEdit, editorListener);

        activeEditor = ed;
        // when inserting the editor into the last row, the body is
        // scrolling and edit is being cancelled
        ignoreScroll = true;
        ed.startEdit((com.google.gwt.user.client.Element) grid.getView().getCell(row, col), m.get(field));
        DeferredCommand.addCommand(new Command() {
            public void execute() {
                ignoreScroll = false;
            }
        });

        // ---------------------------------------------------------------------------------------------------------
    };

    /**
     * {@inheritDoc}
     */
    @Override
    protected void onEditComplete(CellEditor ed, Object value, Object startValue) {

        // LNT [Aug 2, 2010]: this method is copied from the superclass with some specific enhancements. These are
        // mentioned clearly in the code.

        editing = false;
        activeEditor = null;
        ed.removeListener(Events.SpecialKey, editorListener);
        ed.removeListener(Events.Complete, editorListener);
        ed.removeListener(Events.CancelEdit, editorListener);
        Record r = activeRecord;
        String field = cm.getDataIndex(ed.col);

        if ((value == null && startValue != null) || (value != null && !value.equals(startValue))) {

            // LNT [Aug 2, 2010]: enhance GridEvent to set cell editor for event handlers
            // --------------------------------------------------------------------------
            ExtendedGridEvent<M> ge = new ExtendedGridEvent<M>(grid);
            ge.setCellEditor(ed);
            // --------------------------------------------------------------------------

            ge.setRecord(r);
            ge.setProperty(field);
            ge.setValue(value);
            ge.setStartValue(startValue);
            ge.setRowIndex(ed.row);
            ge.setColIndex(ed.col);

            if (grid.fireEvent(Events.ValidateEdit, ge)) {
                r.setValid(ge.getProperty(), ed.getField().isValid(true));
                r.set(ge.getProperty(), ge.getValue());
                grid.fireEvent(Events.AfterEdit, ge);
            }
        }
    }

    /**
     * Enhanced version of {@link GridEvent} to provide access to active {@link CellEditor}.
     * 
     * @param <M>
     * @author Truong Chau Lien Ngoc (lienngoc@gmail.com)
     */
    public static class ExtendedGridEvent<M extends ModelData> extends GridEvent<M> {
        private CellEditor cellEditor;

        /**
         * @param grid
         */
        public ExtendedGridEvent(Grid<M> grid) {
            super(grid);
        }

        /**
         * @param grid
         * @param event
         */
        public ExtendedGridEvent(Grid<M> grid, Event event) {
            super(grid, event);
        }

        /**
         * Returns current value of cellEditor.
         * 
         * @return the cellEditor.
         */
        public CellEditor getCellEditor() {
            return cellEditor;
        }

        /**
         * Sets new value for cellEditor.
         * 
         * @param cellEditor
         *            the cellEditor to set.
         */
        public void setCellEditor(CellEditor cellEditor) {
            this.cellEditor = cellEditor;
        }
    }
}
